package assignment;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.namespace.QName;
import javax.xml.stream.FactoryConfigurationError;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.XMLStreamWriter;

import assignment.jaxb.Card;
import assignment.jaxb.CardDetails;

/**
 * This class is a small demo of how to use StAX and JAXB to produce a stream of
 * objects without loading the full document at the same time.
 */
public class JaxbStreamer {
	private static final String filename = "large.xml.gz";

	public static void main(String[] args) throws XMLStreamException, FactoryConfigurationError, JAXBException, IOException {
		
		BufferedReader in = new BufferedReader(new InputStreamReader(System.in));

		int option = 0;
		while (option == 0) {
			System.out.println("Options: "); // present the user some options
			System.out.println(" 1 - Randomize " + filename); // create a new
																// randomized
																// xml file
			System.out.println(" 2 - Read " + filename); // load the file as a
															// stream
			try {
				option = Integer.parseInt(in.readLine());
			} catch (Exception e) {
				System.out.println("invalid option: " + e.getMessage());
				continue;
			}
			switch (option) {
			case 1:
				FileOutputStream fos = new FileOutputStream(filename);
				GZIPOutputStream gos = new GZIPOutputStream(fos);
				writeStreamWithJaxb(gos); // large file, so we will gzip it
				gos.close();
				fos.close();
				break;
			case 2:
				FileInputStream fis = new FileInputStream(filename);
				GZIPInputStream gis = new GZIPInputStream(fis);
				readStreamWithJaxb(gis);
				gis.close();
				fis.close();
				break;
			default:
				System.out.println("Invalid option.");
				break;
			}
		}
	}

	/**
	 * Write a large number of cards as xml into an output stream.
	 * 
	 * @param os
	 *            the target output stream
	 * @throws XMLStreamException
	 * @throws FactoryConfigurationError
	 * @throws JAXBException
	 */
	private static void writeStreamWithJaxb(OutputStream os) throws XMLStreamException, FactoryConfigurationError, JAXBException {
		
		//Initialize JAXB
		JAXBContext jaxb = JAXBContext.newInstance(Card.class);
		Marshaller marshaller = jaxb.createMarshaller();
		marshaller.setProperty(Marshaller.JAXB_FRAGMENT, true);
		String namespace = "http://dei.uc.pt/eai/assignment1";

		// prepare the document
		XMLStreamWriter streamWriter = XMLOutputFactory.newInstance().createXMLStreamWriter(os);
		streamWriter.writeStartDocument("UTF-8", "1.0");

		streamWriter.writeStartElement("a1", "cards", namespace);// write the
																	// first
																	// element
		streamWriter.writeNamespace("a1", namespace); // write target namespace
		streamWriter.writeNamespace("xsi",
				"http://www.w3.org/2001/XMLSchema-instance"); // auxiliary
																// namespace
		streamWriter.writeAttribute(
				"http://www.w3.org/2001/XMLSchema-instance", "schemaLocation",
				namespace + " assignment1.xsd"); // for validation uses

		// create a large number of card objects
		for (int i = 0; i < 10000; i++) {
			Card card = new Card();
			card.setCoreClock(randomString());
			card.setGPU(randomString());
			card.setGraphicCardName(randomString());
			card.setImageFileUrl(randomString());
			card.setInterface(randomString());
			card.setManufacturer(randomString());
			card.setMemoryBandwidth(randomString());
			card.setMemoryClock(randomString());
			card.setPixelFillRate(randomString());
			card.setReleaseDate(randomString());
			card.setSeries(randomString());
			card.setShaderOperations(randomString());
			card.setTextureFillRate(randomString());
			card.setUrl(randomString());
			CardDetails details = card.getDetails();
			details.setDirectXCompliance(randomString());
			details.setFragmentPipelines(randomString());
			details.setFramebuffer(randomString());
			details.setGpuRatingFeatures(randomDouble());
			details.setGpuRatingFeaturesVotes(randomInt());
			details.setGpuRatingOverall(randomDouble());
			details.setGpuRatingOverallVotes(randomInt());
			details.setGpuRatingOverclockability(randomDouble());
			details.setGpuRatingOverclockabilityVotes(randomInt());
			details.setGpuRatingPerformance(randomDouble());
			details.setGpuRatingPerformanceVotes(randomInt());
			details.setGpuRatingPrice(randomDouble());
			details.setGpuRatingPriceVotes(randomInt());
			details.setGpuRatingSilence(randomDouble());
			details.setGpuRatingSilenceVotes(randomInt());
			details.setMemoryBusType(randomString());
			details.setMemoryType(randomString());
			details.setNoiseLevel(randomString());
			details.setOpenGLCompliance(randomString());
			details.setProcess(randomString());
			details.setPsvsVersion(randomString());
			details.setRasterOperators(randomString());
			details.setTextureUnits(randomString());
			details.setVertexPipelines(randomString());

			// write each card object at a time
			marshaller.marshal(new JAXBElement<Card>(new QName("card"),
					Card.class, card), streamWriter);
			System.out.println("wrote random card " + (i + 1));
		}

		// closing root-element and documment
		streamWriter.writeEndElement();
		streamWriter.writeEndDocument();
	}

	private static int randomInt() {
		return (int) (Math.random() * 1000000);
	}

	private static double randomDouble() {
		return Math.random();
	}

	private static String randomString() {
		return "randomString" + randomInt();
	}

	/**
	 * Read an input stream through StAX. Reads the root-element through StAX
	 * and the cards through JAXB. Creates the cards one by one allowing them to
	 * be garbage collected.
	 * 
	 * @param is
	 *            the stream to read the cards from.
	 * @throws XMLStreamException
	 * @throws FactoryConfigurationError
	 * @throws JAXBException
	 */
	private static void readStreamWithJaxb(InputStream is) throws XMLStreamException, FactoryConfigurationError, JAXBException {
		//create a StAX reader
		XMLStreamReader streamReader = XMLInputFactory.newInstance().createXMLStreamReader(is);

		//Initialize JAXB
		JAXBContext jaxb = JAXBContext.newInstance(Card.class);
		Unmarshaller unmarshaller = jaxb.createUnmarshaller();

		int card_num = 0; // will count the number of cards read here

		
		while (streamReader.hasNext()) { // while not at the end of the document

			// if we found a new card
			if (streamReader.getEventType() == XMLStreamReader.START_ELEMENT
					&& streamReader.getLocalName().equals("card")) {
				//read the card using JAXB
				JAXBElement<Card> cardE = unmarshaller.unmarshal(streamReader,Card.class);
				System.out.println(cardE.getValue()); // print the card
				System.out.println("Printed card " + (++card_num));
			} else // otherwise if we found something else, just skip it
				streamReader.next();
		}
	}
}